/*-------------------------------------------------------------------------
   timer.c
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

// #include <stdbool.h>
#include <stdio.h>
#include "cc_compat.h"
#include "chip.h"
#include "timer.h"
#include "io.h"

// Usually this is enabled for debug output but occasionally you
// need to disable it to speed everything up.
#define PRINT_TSTAMPS 1

// One milisecond free running counter incremented
// via a timer interrupt
// will overflow in 32 seconds.
data volatile int ms_timer;

// Long term ms_timer.  Overflows in 24.8 days
// used for delays that need to be > 32 seconds.
data volatile long ms_timer_ext;

//  Timer 1 is for exclusive use of the 1-wire code right now.
#if 0
void intsrv_timer1(void) interrupt 3
{
}
#endif

#if 0
// gpt 0, 1, and 2 are unused
void intsrv_gptimer0(void) interrupt 20
{
	gpt[GPTPF] = 0x01;
}

void intsrv_gptimer1(void) interrupt 21
{
	gpt[GPTPF] = 0x02;
}

void intsrv_gptimer2(void) interrupt 22
{
	gpt[GPTPF] = 0x04;
}
#endif

void intsrv_gptimer3(void) interrupt 23
{
	ms_timer_ext++;
	ms_timer++;
	// Clear the interrupt flag
	GPTPF = 0x08;
}

void timer_gpt3_init(void)
{
	/* enable the output heartbeat on TP167 */
	IO_OUTPUT_ONLY(GPIO_2D);

//	ms_timer = 0;
//	ms_timer_ext = 0;

	// Enable GPT3 to count 1ms
	GPT3H = 0;
	GPT3L = 32;
	GPTPF = 0x80;
	GPTCFG |= 0x08;
	P1IE7 = 1;
}

void short_delay(unsigned int n)
{
	unsigned int t;
#ifdef SDCC
	// with this loop, sdcc gives 3.1uS (again, using 'D' command),
	// expect:  "warning 110:  conditional flow changed by
	//          optimizer:  so said EVELYN the modified DOG"
	// (i can't figure out how to get rid of the warning)
	for (t=0; t<n; t++)
	{
		(void)IO_ACTIVE_HIGH(GPIO_00);
	}
#else
	// with this loop, keil gives 1.7uS (again, using 'D' command),
	for (t=0; t<n; t++)
	{
		if (IO_ACTIVE_HIGH(GPIO_00)) /* nothing */ ;
	}
#endif
}

// ms_timer is a multi byte number so we have to
// check for a partial read during an increment.
// The number is signed so we can easily deal with
// rollover.
int get_ms_timer(void)
{
	int tval;
	do
	{
		tval = ms_timer;
	}
	while (tval != ms_timer);

	return tval;
}

unsigned char check_timer(int time0, int duration)
{
	int expire;

	expire = time0+duration;

	if (get_ms_timer() - expire > 0 )
	{
		return 1;
	}

	return 0;
}

void ms_delay_block(int duration)
{
	int now;

	now = get_ms_timer();
	while (!check_timer(now,duration))
		;
}

#if PRINT_TSTAMPS
void print_tstamp(void)
{
	unsigned long tval;

	do
	{
		tval = (unsigned long)ms_timer_ext;
	}
	while (tval != (unsigned long) ms_timer_ext);

	printf("%lu:",tval);

}
#else
void print_tstamp(void) {}
#endif

long get_ms_timer_ext(void)
{
	long tval;
	do
	{
		tval = ms_timer_ext;
	}
	while (tval != ms_timer_ext);

	return tval;
}

unsigned char check_timer_ext(long time0, long duration)
{
	long expire;

	expire = time0+duration;

	if (get_ms_timer_ext() - expire > 0 )
	{
		return 1;
	}

	return 0;
}


/* by default, the watchdog will reset the core, and all of the chip
 * _except_ the gpios.  we could optionally only reset the 8051 (not
 * what we want), or additionally reset the gpios.  while we could
 * conceivably want that, it's not what the 3700 did, so we'll leave
 * things as default, for now.
 *
 * no "watchdog_pet()" routine is needed yet.
 *
 * we use the internal oscillator because the primary (only?) use
 * of the watchdog right now is to get us out of stop mode, and
 * we switch to the internal osc in stop mode.
 */


void watchdog(void) interrupt 5
{
#ifdef LJMP_WORKAROUND
	_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
#endif
}

/* timeout is in milliseconds.  granularity is roughly 31ms.
 */
void watchdog_start(unsigned int ms)
{
	unsigned int t;
	t = (ms + 16) / 32;
	t *= 2;  // we want the time to the half-time interrupt
	if (t < 3) t = 3;

	WDTPF = 0x03;
	WDT = t >> 2;				// high 8 bits
	LEDCFG = (t & 0x3) << 6;	// low 2 bits

	// use internal oscillator, enable watchdog reset, and
	// halftime warning seems to be necessary -- in fact,
	// not setting either of bit 0 or bit 1 seems to disable
	// the whole thing.
	WDTCFG = 0x83;
}

void watchdog_stop(void)
{
	// disable the timer by writing bits 6 - 3 to 1001, and disable the events
	WDTCFG = 0xc8;
	WDTPF = 0x03;
	WDT = 0;
}

/* force a reboot using the watchdog */
void watchdog_reboot(void)
{
	CRY32CR = 0x15;
	WDTCFG = 0x83;
	WDT = 4;
	while (1) /* spin */;
}
